use apikit::ApiError;
use async_trait::async_trait;
use axum::{
    extract::{FromRequest, Request},
    response::IntoResponse,
};
use axum_typed_multipart::{FieldMetadata, TryFromChunks, TypedMultipartError};
use bytes::{Bytes, BytesMut};
use futures::Stream;
use futures_util::stream::StreamExt;
use thiserror::Error;
use validator::SelfValidator;

use crate::{
    dto::{FileContent, FileItem},
    error::{BadRequest, EntityError, MapEntityError},
    param::Multipart,
};

use super::error_to_response;

#[derive(Debug, Error, ApiError)]
pub enum MultipartRejection {
    #[error(transparent)]
    Axum(#[from] EntityError),

    #[error(transparent)]
    Validate(#[from] BadRequest),
}

impl IntoResponse for MultipartRejection {
    fn into_response(self) -> axum::response::Response {
        error_to_response(self)
    }
}

impl<T, S> FromRequest<S> for Multipart<T>
where
    T: SelfValidator + Send + axum_typed_multipart::TryFromMultipart,
    S: Send + Sync,
{
    type Rejection = MultipartRejection;

    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
        let data: T = axum_typed_multipart::TypedMultipart::from_request(req, state)
            .await
            .map_entity_error()?
            .0;
        data.validate(None, None)
            .map_err(|e| BadRequest::from(e.to_string()))?;
        Ok(Self(data))
    }
}

#[async_trait]
impl TryFromChunks for FileItem {
    async fn try_from_chunks(
        mut chunks: impl Stream<Item = Result<Bytes, TypedMultipartError>> + Send + Sync + Unpin,
        metadata: FieldMetadata,
    ) -> Result<Self, TypedMultipartError> {
        let mut bytes = BytesMut::new();
        while let Some(chunk) = chunks.next().await {
            let chunk = chunk?;
            bytes.extend_from_slice(&chunk);
        }
        Ok(Self {
            content_type: metadata.content_type,
            file_name: metadata.file_name,
            content: FileContent {
                bytes: bytes.freeze(),
            },
        })
    }
}
